using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using OhioState.Math;

namespace OhioState.Graphics.Proxy
{
    /// <summary>
    /// Container that represents a transfer function.
    /// Has color and alpha splines and a tag to tell
    /// it whether it is interpolating in HSV or RGB space.
    /// 
    /// </summary>
    [Serializable()]
    public class TransferFunction : Texture1D, IPreviewable
    {
        //
        // splines and HSV tag
        //
        private Spline colorPts, alphaPts;
        private bool _HSV;
        /// <summary>
        /// Texel format
        /// </summary>
        protected static TexelFormat DefaultTexelFormat = new TexelFormat();


        #region Constructors
        /// <summary>
        /// Main constructor
        /// When creating a transfer function manually this should be used.
        /// It ensures that the transfer function does not violate the precondition
        /// that it is not empty and also, forces it to have a name.
        /// Two things a transfer function should have at all times.
        /// </summary>
        /// <param name="name">The name of the transfer function.</param>
        /// <param name="fill">Specifies whether the transfer function is empty or not.</param>
        public TransferFunction(String name, bool fill)
            : base(name, DefaultTexelFormat)
        {
            _HSV = true;
            this._width = 256;
            TexelFormat texFormat = this.TexelType;
            texFormat.Format = ImageFormat.RGBA;
            this.TexelType = texFormat;

            colorPts = new Spline();
            alphaPts = new Spline();

            if (fill)
            {
                AddColorKnot(new ColorKnot(0.0f, 0.0f, 0.0f, 1.0f));
                AddColorKnot(new ColorKnot(1.0f, 1.0f, 0.0f, 0.0f));
                AddAlphaKnot(new AlphaKnot(0.0f, 0.0f));
                AddAlphaKnot(new AlphaKnot(1.0f, 0.25f));
            }
        }

        /// <summary>
        /// Copy constructor - create a new instance that is a copy of the given instance.
        /// Similar to Clone() but the memory is not allocated by this object.
        /// </summary>
        /// <param name="source">Transfer function to construct the copy from.</param>
        public TransferFunction(TransferFunction source):base(source.Width)
        {
            Name = source.Name;
            _HSV = source.HSV;

            colorPts = new Spline();
            alphaPts = new Spline();

            int i;

            for (i = 0; i < source.ColorCount(); i++)
            {
                ColorKnot currentKnot = (ColorKnot)source.GetColorKnot(i);
                ColorKnot newKnot = new ColorKnot(currentKnot.Pos, currentKnot.Color.A, currentKnot.Color.R, currentKnot.Color.G, currentKnot.Color.B);
                this.AddColorKnot(newKnot);
            }

            for (i = 0; i < source.AlphaCount(); i++)
            {
                AlphaKnot currentKnot = (AlphaKnot)source.GetAlphaKnot(i);
                AlphaKnot newKnot = new AlphaKnot(currentKnot.Pos, currentKnot.Alpha);
                this.AddAlphaKnot(newKnot);
            }
        }
        #endregion


        /// <summary>
        ///
        /// Public properties
        ///  - setting the HSV tag
        ///  - get/set texture width
        ///
        /// </summary>
        public bool HSV
        {
            get{ return _HSV; }
            set
            {
                _HSV = value;
                FireUpdateChangingEvent(this);
            }
        }

        //public int Width
        //{
        //    get { return width; }
        //    set { width = value; }
        //}

        #region IPreviewable

        /// <summary>
        /// GetPreview function - returns an icon that depicts the transfer function.
        /// I have decided to make the preview not reflect any of the alpha values
        /// of the transfer function.  It is a little clearer to see them when it is done
        /// like this.  But this could be confusing if you have multiple functions that
        /// have the same colors but different alphas.
        /// </summary>
        /// <param name="bitmap"></param>
        public void Preview(Bitmap bitmap)
        {
            //Graphics g = Graphics.FromImage(bitmap);
            Spline spline = MergeSplines();
            ColorKnot k;

            //g.FillRectangle(new SolidBrush(Color.White), g.ClipBounds);

            for (int j = 0; j < bitmap.Height; j++)
            {
                for (int i = 0; i < bitmap.Width; i++)
                {
                    k = spline.Evaluate(((float)i) / ((float)bitmap.Width - 1.0f)) as ColorKnot;
                    bitmap.SetPixel(i, j, k.Color.TrueColor);
                    //g.DrawLine(new Pen(k.Color.TrueColor, 1), i, 0, i, bitmap.Height - 1);
                }
            }
        }

        #endregion

        /// <summary>
        /// Output a texture buffer from the splines.
        /// The width of the texture is also returned.  256 is typical.
        /// </summary>
        /// <param name="xDim">The width of the texture; is assigned in the function.</param>
        /// <returns>The texture buffer.</returns>
        public override byte[] GetTextureByte(out int xDim)
        {
            xDim = _width * 4;
            byte[] tex = new byte[_width * 4];
            Spline spline = MergeSplines();
            ColorKnot k;

            for (int i = 0; i < _width; i++)
            {
                k = (ColorKnot)spline.Evaluate(((float)i) / ((float)_width - 1.0f));
                tex[4 * i] = (byte)(k.Color.R * 255.0f);
                tex[4 * i + 1] = (byte)(k.Color.G * 255.0f);
                tex[4 * i + 2] = (byte)(k.Color.B * 255.0f);
                tex[4 * i + 3] = (byte)(k.Color.A * 255.0f);
            }

            return tex;
        }

        /// <summary>
        /// Merge the color and alpha spline into a single spline.
        /// In RGB space evaluate both splines whenever
        /// a knot is found on either spline.
        /// HSV space is the same as RGB space except since drawing to the
        /// screen occurs in RGB space, you must also evaluate the splines
        /// where the Hue changes from one color to the another.
        /// </summary>
        /// <returns>The resulting merged spline.</returns>
        public Spline MergeSplines()
        {
            ColorKnot color, alpha, k;
            int i;

            //
            // Set the tags for all knots to reflect whether we are interpolating
            //  in HSV or RGB space.
            //
            for (i = 0; i < colorPts.Count; i++)
            {
                k = (ColorKnot)colorPts[i];
                k.HSV = _HSV;
            }

            // Old method: Evaluate the splines 256 times evenly spaced
            // This has sampling issues
            //
            // Old Method... Simple, slow to draw, and not very accurate
            //
            /*float sub = 1.0f/256.0f;
            color = (ColorKnot)colorPts.Evaluate(0.0f);
            alpha = (ColorKnot)alphaPts.Evaluate(0.0f);
            k = new ColorKnot(0.0f, alpha.Color.A, color.Color.R, color.Color.G, color.Color.B);
            Spline spline = new Spline(k);

            for(float i = sub; i <= 1.0f; i += sub)
            {
                color = (ColorKnot)colorPts.Evaluate(i);
                alpha = (ColorKnot)alphaPts.Evaluate(i);
                k = new ColorKnot(i, alpha.Color.A, color.Color.R, color.Color.G, color.Color.B);
                spline.AddKnot(k);
            }*/

            float last;
            List<float> temp = new List<float>();

            //
            // Create list of all points that need to be evaluated
            //
            // In RGB space just take all of the color point positions
            // In HSV space take all of the color point positions as well
            //  as where ever there is a transition in the Hue
            //
            temp.Add(0.0f);
            temp.Add(1.0f);
            if (!_HSV)
            {
                for (i = 0; i < colorPts.Count; i++)
                {
                    k = (ColorKnot)colorPts[i];
                    temp.Add(k.Pos);
                }
            }
            else
            {
                ColorKnot a, b;
                float hue_a, hue_b, pos_a, pos_b;
                a = (ColorKnot)colorPts[0]; // this is dangerous but there should always be a knot in the spline
                for (i = 1; i < colorPts.Count; i++)
                {
                    b = (ColorKnot)colorPts[i];
                    pos_a = a.Pos;
                    pos_b = b.Pos;
                    hue_a = a.Color.H;
                    hue_b = b.Color.H;

                    //
                    // Be careful about the hues here. Hues don't matter if the saturation is 0.
                    //
                    if ((a.Color.S == 0.0f) || (b.Color.S == 0.0f))
                    {
                        temp.Add(pos_a);
                    }
                    else
                    {
                        while (pos_a < pos_b)
                        {
                            temp.Add(pos_a);
                            float hue_ceil = (int)hue_a + 1;
                            float hue_floor = (hue_a == (int)hue_a) ? hue_a - 1 : (int)hue_a;
                            if ((hue_a < hue_b) && (hue_ceil < hue_b))
                            {
                                float factor = (hue_ceil - hue_a) / (hue_b - hue_a);
                                hue_a = hue_ceil;
                                pos_a = (pos_b - pos_a) * factor + pos_a;
                            }
                            else if ((hue_a > hue_b) && (hue_floor > hue_b))
                            {
                                float factor = (hue_a - hue_floor) / (hue_a - hue_b);
                                hue_a = hue_floor;
                                pos_a = (pos_b - pos_a) * factor + pos_a;
                            }
                            else
                            {
                                pos_a = pos_b;
                            }
                        }
                    }

                    a = b;
                }
                temp.Add(a.Pos);
            }
            //
            // Now add the positions of the alpha points
            //
            for (i = 0; i < alphaPts.Count; i++)
            {
                k = (ColorKnot)alphaPts[i];
                temp.Add(k.Pos);
            }

            //
            // Create a new spline by evaluating at all the positions
            //  we previously picked out
            //
            temp.Sort();
            last = (float)temp[0];
            color = (ColorKnot)colorPts.Evaluate(last);
            alpha = (ColorKnot)alphaPts.Evaluate(last);
            k = new ColorKnot(last, alpha.Color.A, color.Color.R, color.Color.G, color.Color.B);
            Spline spline = new Spline(k);

            for (i = 1; i < temp.Count; i++)
            {
                if ((float)temp[i] > last) // Remove duplicates
                {
                    last = (float)temp[i];
                    color = (ColorKnot)colorPts.Evaluate(last);
                    alpha = (ColorKnot)alphaPts.Evaluate(last);
                    k = new ColorKnot(last, alpha.Color.A, color.Color.R, color.Color.G, color.Color.B);
                    spline.AddKnot(k);
                }
            }

            return spline;
        }

        #region Accessors
        /// <summary>
        /// Add Knots
        /// </summary>
        /// <param name="k">knot</param>
        public void AddColorKnot(Knot k)
        {
            colorPts.AddKnot(k);
            FireUpdateChangedEvent(this);
        }
        /// <summary>
        /// Add Alpha Knot
        /// </summary>
        /// <param name="k">knot</param>
        public void AddAlphaKnot(Knot k)
        {
            alphaPts.AddKnot(k);
            FireUpdateChangedEvent(this);
        }
        /// <summary>
        /// Remove knot
        /// </summary>
        /// <param name="k"></param>
        public void RemoveKnot(Knot k)
        {
            colorPts.RemoveKnot(k);
            alphaPts.RemoveKnot(k);
            FireUpdateChangedEvent(this);
        }
        /// <summary>
        /// Move a knot
        /// </summary>
        /// <param name="k"></param>
        /// <param name="pos"></param>
        public void MoveKnot(Knot k, float pos)
        {
            k.Pos = pos;
            FireUpdateChangingEvent(this);
            FireUpdateChangedEvent(this);
        }
        /// <summary>
        /// Change Knot values
        /// </summary>
        /// <param name="k">knot</param>
        /// <param name="c">color</param>
        public void KnotColor(ColorKnot k, Color c)
        {
            ((ColorKnot)k).Color.Color = c;
            FireUpdateChangingEvent(this);
            FireUpdateChangedEvent(this);
        }
        /// <summary>
        /// Change alpha knot
        /// </summary>
        /// <param name="k">alpha knot</param>
        /// <param name="a">float</param>
        public void KnotAlpha(AlphaKnot k, float a)
        {
            ((AlphaKnot)k).Alpha = a;
            FireUpdateChangingEvent(this);
            FireUpdateChangedEvent(this);
        }
        
        /// <summary>
        /// Evaluate Color
        /// </summary>
        /// <param name="pos">position</param>
        /// <returns>knot</returns>
        public Knot EvaluateColor(float pos) { return colorPts.Evaluate(pos); }
        /// <summary>
        /// Evaluate alpha knot
        /// </summary>
        /// <param name="pos">position</param>
        /// <returns>alpha knot</returns>
        public Knot EvaluateAlpha(float pos) { return alphaPts.Evaluate(pos); }
        /// <summary>
        /// Get color knot
        /// </summary>
        /// <param name="index">index</param>
        /// <returns>color knot</returns>
        public Knot GetColorKnot(int index) { return colorPts[index]; }
        /// <summary>
        /// Get color knot
        /// </summary>
        /// <param name="index">index</param>
        /// <returns>alpha knot</returns>
        public Knot GetAlphaKnot(int index) { return alphaPts[index]; }
        /// <summary>
        /// Get color count
        /// </summary>
        /// <returns>count of color points</returns>
        public int ColorCount() { return colorPts.Count; }
        /// <summary>
        /// Get alpha count
        /// </summary>
        /// <returns>count of alpha points</returns>
        public int AlphaCount() { return alphaPts.Count; }
        #endregion

        #region Clone
        /// <summary>
        /// Clone - make a duplicate copy of this transfer function and return it.
        /// </summary>
        /// <returns>The cloned transfer function.</returns>
        public override TextureProxy Clone()
        {
            int i;

            TransferFunction transFunc = new TransferFunction(Name, false);
            transFunc.HSV = _HSV;

            for(i = 0; i < ColorCount(); i++)
            {
                ColorKnot currentKnot = (ColorKnot)GetColorKnot(i);
                ColorKnot newKnot = new ColorKnot(currentKnot.Pos, currentKnot.Color.A, currentKnot.Color.R, currentKnot.Color.G, currentKnot.Color.B);
                transFunc.AddColorKnot(newKnot);
            }

            for (i = 0; i < AlphaCount(); i++)
            {
                AlphaKnot currentKnot = (AlphaKnot)GetAlphaKnot(i);
                AlphaKnot newKnot = new AlphaKnot(currentKnot.Pos, currentKnot.Alpha);
                transFunc.AddAlphaKnot(newKnot);
            }

            return transFunc;
        }
        #endregion

        #region File operations

        /// <summary>
        /// LoadFromFile - static function that loads a transfer function from a file
        /// using binary deserialization.  Returns null if there is a problem.
        /// </summary>
        /// <param name="filename">Path to the file to load.</param>
        /// <returns>Loaded transfer function.</returns>
        public static TransferFunction LoadFromFile(String filename)
        {
            TransferFunction obj;

            Stream stream = File.Open(filename, FileMode.Open);
            BinaryFormatter formatter = new BinaryFormatter();

            try
            {
                obj = (TransferFunction)formatter.Deserialize(stream);
            }
            catch
            {
                obj = null;
            }
            stream.Close();

            return obj;
        }

        /// <summary>
        /// SaveToFile - function that saves a transfer function to a file
        /// using binary serialization.  This is not static and is simply called on the
        /// object that needs to be saved.
        /// </summary>
        /// <param name="filename">Path to the file to save transfer function into.</param>
        public void SaveToFile(String filename)
        {
            //
            // Make a clone to save.  The marshalling done by remoting causes problems with 
            // serialization.
            //
            TransferFunction obj = (TransferFunction)this.Clone();

            Stream stream = File.Open(filename, FileMode.Create);
            BinaryFormatter formatter = new BinaryFormatter();

            formatter.Serialize(stream, obj);
            stream.Close();
        }
        #endregion
    }
}
